﻿using Nes.Core.Buss;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Nes.Core.PPUS
{
    /// <summary>
    /// 7  bit  0
    /// ---- ----
    /// VPHB SINN
    /// |||| ||||
    /// |||| ||++- Base nametable address
    /// |||| ||    (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
    /// |||| |+--- VRAM address increment per CPU read/write of PPUDATA
    /// |||| |     (0: add 1, going across; 1: add 32, going down)
    /// |||| +---- Sprite pattern table address for 8x8 sprites
    /// ||||       (0: $0000; 1: $1000; ignored in 8x16 mode)
    /// |||+------ Background pattern table address(0: $0000; 1: $1000)
    /// ||+------- Sprite size(0: 8x8 pixels; 1: 8x16 pixels – see PPU OAM#Byte 1)
    /// |+-------- PPU master/slave select
    /// |          (0: read backdrop from EXT pins; 1: output color on EXT pins)
    /// +--------- Generate an NMI at the start of the
    /// </summary>
    public class PPUCtrl
    {
        private byte data;

        public byte Data { get { return data; } set { data = value; } }

        private Int32[] BaseNameTableAddrArr = [0x2000, 0x2400, 0x2800, 0x2c00];

        public Int32 BaseNameTableAddr
        {
            get
            {
                return BaseNameTableAddrArr[data & 0x03];
            }
            set
            {
                if (value < 4)
                {
                    data |= (Byte)value;
                }
                else
                {
                    throw new ArgumentException();
                }
            }
        }

        public Int32 VramIncrementStepSize
        {
            get
            {
                return (data & 0x04) == 0x04 ? 32 : 1;
            }
            set
            {
                if (value == 32)
                {
                    data |= 0b00000100;
                }
                else
                {
                    data &= 0b11111011;
                }
            }
        }

        public Int32 SpritePatternTableAddress
        {
            get
            {
                return (data & 0x08) == 0x08 ? 0x1000 : 0;
            }
            set
            {
                if (value == 0x1000)
                {
                    data |= 0b00001000;
                }
                else
                {
                    data &= 0b11110111;
                }
            }
        }

        public Int32 BackgroundPatternTableAddress
        {
            get
            {
                return (data & 0x10) == 0x10 ? 0x1000 : 0;
            }
            set
            {
                if (value == 0x1000)
                {
                    data |= 0b00010000;
                }
                else
                {
                    data &= 0b11101111;
                }
            }
        }

        public Int32 SpriteSize
        {
            get
            {
                return (data & 0x20) == 0x20 ? 16 : 8;
            }
            set
            {
                if (value == 16)
                {
                    data |= 0b00100000;
                }
                else
                {
                    data &= 0b11011111;
                }
            }
        }

        public Boolean PPUIO
        {
            get
            {
                return (data & 0x40) == 0x40;
            }
            set
            {
                if (value)
                {
                    data |= 0b01000000;
                }
                else
                {
                    data &= 0b10111111;
                }
            }
        }

        public Boolean NMIEnabled
        {
            get
            {
                return (data & 0x80) == 0x80;
            }
            set
            {
                if (value)
                {
                    data |= 0b10000000;
                }
                else
                {
                    data &= 0b01111111;
                }
            }
        }
    }

    /// <summary>
    /// 7  bit  0
    /// ---- ----
    /// BGRs bMmG
    /// |||| ||||
    /// |||| |||+- Greyscale(0: normal color, 1: produce a greyscale display)
    /// |||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide
    /// |||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
    /// |||| +---- 1: Show background
    /// |||+------ 1: Show sprites
    /// ||+------- Emphasize red(green on PAL/Dendy)
    /// |+-------- Emphasize green(red on PAL/Dendy)
    /// +--------- Emphasize blue
    /// <para></para>
    /// </summary>
    public class PPUMask
    {
        private byte data;

        public byte Data { get { return data; } set { data = value; } }

        public Boolean Colorful
        {
            get
            {
                return (data & 0x01) != 0x01;
            }
            set
            {
                if (value)
                {
                    data &= 0b1111_1110;
                }
                else
                {
                    data |= 0b0000_0001;
                }

            }
        }

        public Boolean ShowBackgroundLeft8px
        {
            get
            {
                return (data & 0x02) == 0x02;
            }
            set
            {
                if (value)
                {
                    data |= 0b0000_0010;
                }
                else
                {
                    data &= 0b1111_1101;
                }

            }
        }

        public Boolean ShowSpriteLeft8px
        {
            get
            {
                return (data & 0x04) == 0x04;
            }
            set
            {
                if (value)
                {
                    data |= 0b0000_0100;
                }
                else
                {
                    data &= 0b1111_1011;
                }

            }
        }

        public Boolean ShowBackground
        {
            get
            {
                return (data & 0x08) == 0x08;
            }
            set
            {
                if (value)
                {
                    data |= 0b0000_1000;
                }
                else
                {
                    data &= 0b1111_0111;
                }

            }
        }

        public Boolean ShowSprite
        {
            get
            {
                return (data & 0x10) == 0x10;
            }
            set
            {
                if (value)
                {
                    data |= 0b0001_0000;
                }
                else
                {
                    data &= 0b1110_1111;
                }

            }
        }

        ///// <summary>
        ///// ShowSprite      False
        ///// ShowBackground  False
        ///// </summary>
        ///// <returns></returns>
        //[MethodImpl(MethodImplOptions.AggressiveInlining)]
        //public Boolean ShowAllNotBackSprite()
        //{
        //    // 0b0001_1000
        //    return (0b0001_1000 & data) == 0;
        //}

        /// <summary>
        /// ShowSprite      True
        /// ShowBackground  False
        /// Or
        /// ShowSprite      False
        /// ShowBackground  True
        /// </summary>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Boolean ShowBackOrSprite()
        {
            // 0b0001_1000
            return (0b0001_1000 & data) > 0;
        }
        /// <summary>
        /// ShowSprite      True
        /// ShowBackground  True
        /// </summary>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Boolean ShowBackAndSprite()
        {
            return (data & 0x18) == 0x18;
        }

        public Boolean EmphasizeRed
        {
            get
            {
                return (data & 0x20) == 0x20;
            }
            set
            {
                if (value)
                {
                    data |= 0b0010_0000;
                }
                else
                {
                    data &= 0b1101_1111;
                }

            }
        }

        public Boolean EmphasizeGreen
        {
            get
            {
                return (data & 0x40) == 0x40;
            }
            set
            {
                if (value)
                {
                    data |= 0b0100_0000;
                }
                else
                {
                    data &= 0b1011_1111;
                }

            }
        }

        public Boolean EmphasizeBlue
        {
            get
            {
                return (data & 0x80) == 0x80;
            }
            set
            {
                if (value)
                {
                    data |= 0b1000_0000;
                }
                else
                {
                    data &= 0b0111_1111;
                }

            }
        }
    }

    /// <summary>
    /// 7  bit  0
    ///---- ----
    ///VSO. ....
    ///|||| ||||
    ///|||+-++++- PPU open bus.Returns stale PPU bus contents.
    ///||+------- Sprite overflow.The intent was for this flag to be set
    ///||         whenever more than eight sprites appear on a scanline, but a
    ///||         hardware bug causes the actual behavior to be more complicated
    ///||         and generate false positives as well as false negatives; see
    ///||         PPU sprite evaluation.This flag is set during sprite
    ///||         evaluation and cleared at dot 1 (the second dot) of the
    ///||         pre-render line.
    ///|+-------- Sprite 0 Hit.Set when a nonzero pixel of sprite 0 overlaps
    ///|          a nonzero background pixel; cleared at dot 1 of the pre-render
    ///|          line.Used for raster timing.
    ///+--------- Vertical blank has started (0: not in vblank; 1: in vblank).
    ///           Set at dot 1 of line 241 (the line * after* the post-render
    ///           line); cleared after reading $2002 and at dot 1 of the
    ///           pre-render line.
    /// </summary>
    public class PPUStatus
    {
        private byte data;
        public byte Data { get { return data; } set { data = value; } }

        public Boolean VBlankStarted
        {
            get
            {
                return (data & 0x80) == 0x80;
            }
            set
            {
                if (value)
                {
                    data |= 0x80;
                }
                else
                {
                    data &= 0x7F;
                }
            }
        }

        public Boolean Sprite0Hit
        {
            get
            {
                return (data & 0x40) == 0x40;
            }
            set
            {
                if (value)
                {
                    data |= 0b01000000;
                }
                else
                {
                    data &= 0b10111111;
                }

            }
        }

        /// <summary>
        /// 当前扫描线的精灵超过8个标志位
        /// </summary>
        public Boolean SpriteOverflow
        {
            get
            {
                return (data & 0x20) == 0x20;
            }
            set
            {
                if (value)
                {
                    data |= 0b00100000;
                }
                else
                {
                    data &= 0b11011111;
                }

            }
        }
    }

    public class SecondaryOamInfo
    {
        public void SetInto(Byte y, Byte tileIndex, Byte attr, Byte x) 
        {
            Attributes = attr;
            TileIndex = tileIndex;
            X = x;
            Y = y;
        }

        public Byte Attributes { get; set; }
        public Byte TileIndex { get; set; }
        public Byte X { get; set; }
        public Byte Y { get; set; }
        public Boolean IsZero { get; set; }
    }
}
